package com.springboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

/**
 * 配置安全认证。暂时还没有页面，只是放着，可以参考注释。
 *
 * @author yexi
 */

@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {

    /**
     * 应用上下文路径
     */
    private final String adminContextPath = "/admin";

    /**
     * 配置安全认证。
     * <p>
     * HTTP Basic 认证:
     * 一种简单的身份验证方式，它通过在每个请求的 Authorization 请求头中发送用户名和密码的 Base64 编码来进行认证。
     * 配置之后，当用户发起请求时，服务器将返回一个 401 Unauthorized 错误响应，要求用户提供用户名和密码进行认证。
     * 用户可以在请求的 Authorization 请求头中添加 Base64 编码的用户名和密码进行认证。一旦用户提供了正确的用户名和密码，
     * 服务器将返回 200 OK 响应并允许用户访问受保护的资源。
     * <p>
     * CSRF（Cross-Site Request Forgery）跨站请求伪造是一种常见的Web应用程序安全漏洞，攻击者利用该漏洞通过欺骗用户在受信任网站上执行恶意操作。
     * CSRF攻击的原理是攻击者构造一个恶意网站或者篡改合法网站上的链接、表单等，当用户在登录状态下访问这些链接或提交表单时，
     * 浏览器会自动发送带有用户登录凭据（如cookie）的请求到目标网站，从而实现攻击者所期望的操作，例如修改密码、转账等。
     * (常见手段: 1、钓鱼网站 2、跨站点脚本 3、利用不安全的组件 4、社工攻击)
     * 为了有效防御CSRF攻击，开发人员可以采取如下措施：
     * 1、使用CSRF令牌：在用户与应用程序交互期间，应用程序生成一个唯一的CSRF令牌并将其存储在会话中。
     * 在需要保护的请求中，将该令牌包含在请求参数或头部中。服务器在接收到请求时验证令牌的有效性。
     * 这样，攻击者无法预先知道令牌值，无法构造出有效的请求。
     * 2、启用SameSite Cookie属性：通过将Cookie的SameSite属性设置为Strict或Lax，限制了跨站点请求时Cookie的发送，
     * 从而减少了CSRF攻击的可能性。
     * 3、验证来源（Referer）头部：服务器可以验证请求中的Referer头部字段，确保请求来源是合法的。
     * 然而，Referer头部并不是完全可信的，因为它可能被篡改或者在某些情况下被浏览器省略。
     * 4、限制敏感操作的访问权限：对于执行敏感操作的请求，如修改密码、删除账户等，应该要求用户进行额外的身份验证或者输入密码。
     * <p>
     * CSRF攻击的本质是利用了浏览器的Cookie机制，而Cookie是浏览器与服务器之间的一种会话状态的保存机制
     * CSRF攻击利用了浏览器的Cookie机制来实施攻击。在正常情况下，当用户在浏览器登录一个网站时，该网站会将一个包含用户身份验证信息的Cookie发送给浏览器，并保存在用户的本地机器上。每次用户向该网站发出请求时，浏览器会自动附加该Cookie到请求中，以表明用户的身份。
     * CSRF攻击利用了这一机制的特点：浏览器在发送请求时会自动携带Cookie。攻击者构造恶意的请求并将其嵌入到受害者访问的网页中，当受害者打开这个恶意页面时，浏览器会自动将包含合法认证信息的Cookie附加到请求中。这样，目标网站无法区分恶意请求和合法请求，因为它们都带有有效的Cookie。
     * 通过利用被攻击网站的信任关系，攻击者能够欺骗用户的浏览器，使其执行恶意操作，例如更改账户设置、发送资金等。由于浏览器自动附加Cookie，受害者对于所发出的请求并不知情。
     * 为了防范CSRF攻击，开发人员可以采取措施，如使用CSRF令牌、限制敏感操作的访问权限等。这些措施都是为了在用户的浏览器中标记请求并验证其合法性，而不仅仅依赖于Cookie作为身份验证的唯一凭据。
     * <p>
     * CSRF令牌（Cross-Site Request Forgery Token）是一种用于防御CSRF攻击的安全措施。它可以有效地防止攻击者发送伪造的请求，因为攻击者无法获取到合法的CSRF令牌。
     * 下面是CSRF令牌如何防止攻击的原理：
     * 生成随机令牌：服务器会为每个用户会话生成一个唯一的随机令牌，并将其包含在网页中或者作为Cookie发送给浏览器。这个令牌不会暴露用户的身份信息，是伪随机生成的。
     * 令牌验证：当用户提交请求时，浏览器会自动将CSRF令牌附加到请求中。服务器接收到请求后，会验证该请求中的令牌是否与所期望的令牌匹配。如果验证失败，则拒绝处理此请求。
     * 通过这种方式，CSRF令牌在防御CSRF攻击中发挥了重要作用：
     * 攻击者无法获取合法的CSRF令牌：由于令牌是随机生成的，并且与用户的会话相关联，攻击者无法获得用户的CSRF令牌。这意味着攻击者无法构造有效的请求，因为缺少合法的令牌。
     * 请求需要包含令牌以通过验证：由于浏览器会自动附加CSRF令牌到请求中，攻击者无法在用户不知情的情况下窃取或伪造令牌。服务器将验证请求中的令牌，并拒绝与预期令牌不匹配的请求。
     * 防止重放攻击：CSRF令牌通常是一次性的，每个请求都需要使用新的令牌。这样可以防止攻击者通过重放已知的令牌来再次执行攻击。
     * (后续修改)
     * 实际上，CSRF令牌本身也是可以被攻击者窃取的。攻击者如果能够成功获取到用户浏览器中的CSRF令牌，就可以利用这个令牌来伪造合法请求，从而进行CSRF攻击。
     * 攻击者可能会通过以下方式尝试窃取CSRF令牌：
     * 跨站脚本攻击（XSS）：攻击者注入恶意脚本到受信任网站，当用户访问该网站时，恶意脚本可以劫持用户的会话并窃取CSRF令牌。
     * 内部恶意员工：恶意内部员工可能利用其特权地位来访问和窃取用户的CSRF令牌。
     * 社会工程学攻击：攻击者可能通过欺骗、诱导或其他手段来诱使用户主动泄露他们的CSRF令牌。
     * 因此，为了增强CSRF防御的安全性，开发人员需要采取措施来减少CSRF令牌被窃取的风险，并确保令牌的机密性：
     * 使用HTTPS协议：通过使用安全的HTTPS连接，可以使通信过程更加安全，减少令牌被中间人窃取的风险。
     * 设置令牌的HttpOnly属性：将CSRF令牌设置为仅通过HTTP传输，阻止JavaScript代码访问令牌，从而减少XSS攻击对令牌的窃取。
     * 使用短时间有效的令牌：限制CSRF令牌的有效期，使其只在短时间内有效，减少攻击者获取有效令牌的机会。
     * 实施其他安全措施：例如使用双因素身份验证、输入验证等，以增强整体安全性。
     * 总结起来，虽然CSRF令牌可以有效防止攻击者构造伪造请求进行CSRF攻击，但如果令牌本身被攻击者窃取，就会造成安全风险。因此，开发人员需要综合考虑其他安全措施来保护CSRF令牌的机密性和安全性。
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                // 允许所有 /assets/** 路径下的资源可以公开访问，不需要认证。
                .antMatchers(adminContextPath + "/assets/**").permitAll()
                // 允许 /login 路径下的登录页面可以公开访问，不需要认证。
                .antMatchers(adminContextPath + "/login").permitAll()
                // 对于任何其他请求, 要求用户进行认证（即登录）才能访问。
                .anyRequest().authenticated()
                .and()
                // formLogin() 配置使用表单登录方式
                .formLogin()
                // 指定登录页面的路径
                .loginPage(adminContextPath + "/login")
                // 指定登录成功后的处理器
                .successHandler(this.buildSuccessHandler())
                .and()
                // 配置登出操作
                .logout()
                // 指定登出的路径
                .logoutUrl(adminContextPath + "/logout")
                .and()
                // 配置使用 HTTP Basic 认证方式。
                .httpBasic()
                .and()
                // 配置CSRF（Cross-Site Request Forgery）跨站请求伪造保护
                .csrf()
                // 使用 CookieCsrfTokenRepository.withHttpOnlyFalse() 配置CSRF令牌存储库，并设置允许在浏览器中访问CSRF令牌。
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // 忽略一些特定的请求路径，如 /instances 和 /actuator/**，不对其进行CSRF保护。
                // (TIPS: 这些路径是Spring Boot Admin客户端的路径，以便其他的模块可以实现注册。此demo后续会实现，目前先只是理解)
                .ignoringAntMatchers(
                        adminContextPath + "/instances",
                        adminContextPath + "/actuator/**"
                );
    }

    /**
     * 处理用户认证成功后的操作
     */
    private AuthenticationSuccessHandler buildSuccessHandler() {
        SavedRequestAwareAuthenticationSuccessHandler successHandler =
                new SavedRequestAwareAuthenticationSuccessHandler();
        // 设置 redirectTo 参数作为目标URL参数，这将指定在认证成功后重定向到哪个URL。
        successHandler.setTargetUrlParameter("redirectTo");
        // 设置默认的目标URL，即认证成功后默认跳转的URL。
        successHandler.setDefaultTargetUrl(adminContextPath + "/");
        return successHandler;
    }


}